package processor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.LockModeType;

import models.Cart;
import models.CartItem;
import models.CartItem.Status;
import models.LoanInFunding;
import prefix.integration.wm.WMException;
import prefix.integration.wm.WMProcessor;
import prefix.util.JPAUtil;
import repository.ActorRepository;
import repository.CartRepository;
import repository.LoanRepository;
import service.ActorService;
import service.exception.CartException;
import service.exception.CartException.CODE;
import service.impl.CartServiceImpl;

public class CartProcessor extends CartServiceImpl {

    public CartProcessor(Cart cart) {
        super(cart);
    }

    private void addItemToCart(CartItem cartItem, Map<Integer, CartItem> existItems) throws CartException {
        if (existItems == null)
            existItems = new HashMap<Integer, CartItem>();
        if (existItems.containsKey(cartItem.getLoanId()))
            mergeItem(cartItem, existItems);
        else {
            existItems.put(cartItem.getLoanId(), cartItem);
            cartItem.setStatus(Status.PENDING);
            cartItem.setCreateD(new Date());
            cartItem.setUpdateD(new Date());
            cartItem.setCartId(this.getDbcart().getId());
            JPAUtil.em().persist(cartItem);
        }
    }

    public List<CartItem> createCart(List<CartItem> cartItems, Integer lenderId, double sumAmount) throws CartException {

        Cart existCart = CartRepository.getActiveCartByActor(lenderId);
        List<CartItem> unAddItems = null;
        validateWallet(lenderId, sumAmount);
        try {
            JPAUtil.beginTransaction();
            if (existCart == null) {
                Cart cart = new Cart(lenderId, sumAmount);
                cart.setCreateD(new Date());
                cart.setUpdateD(new Date());
                JPAUtil.em().persist(cart);
                this.setDbcart(cart);
            } else {
                this.setDbcart(existCart);
            }
            unAddItems = actureAddItemsToCart(cartItems, lenderId, sumAmount);
            validateUnAddItems(unAddItems, cartItems);
            holdInvestAmount(lenderId, sumAmount);
            JPAUtil.commitTransaction();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CartException(CODE.ERROR_IN_ADD_CART_ITEM, e);
        } finally {
            JPAUtil.closeEM();
        }
        return unAddItems;
    }

    private void holdInvestAmount(int aid, double amount) throws CartException {
        try {
            WMProcessor processor = new WMProcessor();
            processor.buyNotes(aid, new BigDecimal(amount));
        } catch (WMException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new CartException(CODE.UNKNOWN, e);
        }
    }

    private void releaseInvestAmount(int aid, double amount) throws CartException {
        try {
            WMProcessor processor = new WMProcessor();
            processor.releasePendingInvestAmount(aid, new BigDecimal(amount));
        } catch (WMException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new CartException(CODE.UNKNOWN, e);
        }
    }

    private void validateWallet(Integer aid, double amount) throws CartException {

        try {
            ActorService actorService = ActorRepository.getActorServiceById(aid);
            double balance = actorService.getAvailableCash();
            if (balance < amount)
                throw new CartException(CODE.NOT_ENOUGH_FUNDS);
        } catch (CartException e) {
            throw e;
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new CartException(CODE.ERROR_IN_GET_BALANCE, e);
        }
    }

    private void validateUnAddItems(List<CartItem> unAddItems, List<CartItem> cartItems) throws CartException {
        if (unAddItems.size() == cartItems.size())
            throw new CartException(CODE.NO_ITEM_ADD);
    }

    private List<CartItem> actureAddItemsToCart(List<CartItem> cartItems, Integer aid, Double sumAmount) {
        List<CartItem> unAddedItems = new ArrayList<CartItem>();
        Map<Integer, CartItem> existItems = this.getDbcart().getMapItems();
        for (CartItem item : cartItems) {
            try {
                LoanInFunding loan = LoanRepository.getSingleByProperty(LoanInFunding.class, "loanId",
                        item.getLoanId(), LockModeType.PESSIMISTIC_WRITE);
                Double unfundedAmount = loan.getUnfundedAmount();
                if (unfundedAmount == 0)
                    throw new CartException(CODE.NO_AMOUNT_LEFT);
                double requestAmount = item.getAmount();
                item.setAmount(Math.min(unfundedAmount, requestAmount));
                unfundedAmount -= Math.min(unfundedAmount, requestAmount);
                loan.setUnfundedAmount(unfundedAmount);
                loan.setUpdateD(new Date());
                JPAUtil.em().merge(loan);
                addItemToCart(item, existItems);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                unAddedItems.add(item);
                continue;
            }
        }
        return unAddedItems;
    }

    public List<CartItem> addItemsToCart(List<CartItem> cartItems, double sumAmount) throws Throwable {
        validateLoanItem(cartItems, sumAmount);
        validateWallet(this.getDbcart().getAid(), sumAmount);
        try {
            JPAUtil.beginTransaction();
            List<CartItem> unAddItems = actureAddItemsToCart(cartItems, this.dbcart.getAid(), sumAmount);
            this.dbcart = JPAUtil.em().merge(this.dbcart);
            JPAUtil.em().lock(this.dbcart, LockModeType.PESSIMISTIC_WRITE);
            this.dbcart.setAmount(this.dbcart.getAmount() + sumAmount);
            this.dbcart.setUpdateD(new Date());
            holdInvestAmount(this.dbcart.getAid(), sumAmount);
            JPAUtil.commitTransaction();
            return unAddItems;
        } catch (Exception e) {
            e.printStackTrace();
            if (e instanceof CartException)
                throw (CartException) e;
            else
                throw new CartException(CODE.ERROR_IN_ADD_CART_ITEM, e);
        } finally {
            JPAUtil.closeEM();
        }

    }

    private void validateLoanItem(List<CartItem> cartItems, double sumAmount) throws CartException {
        Iterator<CartItem> itrtr = cartItems.iterator();
        Double amountHodler = 0d;
        while (itrtr.hasNext()) {
            CartItem item = itrtr.next();
            if (item.getLoanId() <= 0 || item.getAmount() <= 0)
                throw new CartException(CODE.INVALIDE_CART_ITEM);
            amountHodler += item.getAmount();
        }
        if (amountHodler != sumAmount)
            throw new CartException(CODE.CART_AMOUNT_MISMATCH);
    }

    private void mergeItem(CartItem item, Map<Integer, CartItem> items) throws CartException {
        CartItem existItem = items.get(item.getLoanId());
        if (existItem == null)
            throw new CartException(CODE.INVALIDE_EXIST_ITEM);
        existItem.setAmount(existItem.getAmount() + item.getAmount());
        existItem.setUpdateD(new Date());
        JPAUtil.em().merge(existItem);
    }

    public double removeCartItems(List<Integer> items) throws Throwable {
        Map<Integer, CartItem> itemMap = this.getDbcart().getMapItems();
        try {
            JPAUtil.beginTransaction();
            double removeAmount = 0;
            this.setDbcart(JPAUtil.em().merge(this.getDbcart()));
            JPAUtil.em().lock(this.getDbcart(), LockModeType.PESSIMISTIC_WRITE);
            for (Integer item : items) {
                CartItem cartItem = JPAUtil.em().find(CartItem.class, item, LockModeType.PESSIMISTIC_WRITE);
                if (itemMap.containsKey(cartItem.getLoanId())) {
                    itemMap.remove(cartItem.getLoanId());
                    JPAUtil.em().remove(cartItem);
                    LoanInFunding loan = LoanRepository.getSingleByProperty(LoanInFunding.class, "loanId",
                            cartItem.getLoanId(), LockModeType.PESSIMISTIC_WRITE);
                    loan.setUnfundedAmount(loan.getUnfundedAmount() + cartItem.getAmount());
                    loan.setUpdateD(new Date());
                    JPAUtil.em().merge(loan);
                    removeAmount += cartItem.getAmount();
                } else {
                    throw new CartException(CODE.ITEM_NOT_IN_CART);
                }
            }
            if (removeAmount <= 0)
                throw new CartException(CODE.INVALIDE_REMOVE_AMOUNT);
            this.getDbcart().setAmount(this.getDbcart().getAmount() - removeAmount);
            this.getDbcart().setUpdateD(new Date());
            releaseInvestAmount(this.getDbcart().getAid(), removeAmount);
            JPAUtil.commitTransaction();
            return removeAmount;

        } catch (CartException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CartException(CODE.ERROR_IN_REMOVE_ITEM, e);
        } finally {
            JPAUtil.closeEM();
        }

    }

    public double updateCartItem(List<CartItem> items) throws Throwable {
        Map<Integer, CartItem> itemMap = this.getDbcart().getMapItems();
        try {
            JPAUtil.beginTransaction();
            double amountDif = 0;
            LoanRepository repository = new LoanRepository();
            this.setDbcart(JPAUtil.em().merge(this.getDbcart()));
            JPAUtil.em().lock(this.getDbcart(), LockModeType.PESSIMISTIC_WRITE);
            for (CartItem newItem : items) {
                if (itemMap.containsKey(newItem.getLoanId())) {
                    LoanInFunding loan = repository.getSingleByProperty(LoanInFunding.class, "loanId",
                            newItem.getLoanId(), LockModeType.PESSIMISTIC_WRITE);
                    CartItem existItem = itemMap.get(newItem.getLoanId());
                    double tempAmountDif = 0;
                    tempAmountDif += (newItem.getAmount() - existItem.getAmount());
                    double unfundAmount = loan.getUnfundedAmount();
                    double acceptAmountDif = Math.min(tempAmountDif, unfundAmount);
                    amountDif += tempAmountDif;
                    existItem.setAmount(existItem.getAmount() + acceptAmountDif);
                    existItem.setUpdateD(new Date());
                    JPAUtil.em().merge(existItem);
                    loan.setUnfundedAmount(loan.getUnfundedAmount() - tempAmountDif);
                    loan.setUpdateD(new Date());
                    JPAUtil.em().merge(loan);
                }
            }
            double newCartAmount = this.getDbcart().getAmount() + amountDif;
            this.getDbcart().setAmount(newCartAmount);
            this.getDbcart().setUpdateD(new Date());
            if (amountDif > 0) {
                this.validateWallet(this.getDbcart().getAid(), amountDif);
                this.holdInvestAmount(this.getDbcart().getAid(), amountDif);
            } else if (amountDif < 0) {
                this.releaseInvestAmount(this.getDbcart().getAid(), -amountDif);
            }
            JPAUtil.commitTransaction();
            return amountDif;
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new CartException(CODE.UNKNOWN, e);
        } finally {
            JPAUtil.closeEM();
        }
    }

}
